/**
 * Copyright @2019 Josin All Rights Reserved.
 * Author: Josin
 * Email : xeapplee@gmail.com
 */
#include <fc_xml.h>
#include <fc_stack.h>
#include <fc_string.h>
#include <fc_list.h>

/**
 * @brief NOTICE
 * Some error info store in this static global var.
 * It's not multi-thread safe.
 */
static char error_msg[BUFFER_SIZE];

CXML_NODE *
new_cxml_node()
{
    CXML_NODE *ptr = malloc( sizeof(CXML_NODE) );
    if (!ptr) return NULL;
    e_memzero(ptr, sizeof(CXML_NODE));
    ptr->attrs = new_fcl_list();
    ptr->val   = new_fcl_list();
    ptr->sval  = new_cstring();
    ptr->key   = new_cstring();
    return ptr;
}

CXML *
new_cxml()
{
    CXML *ptr = malloc( sizeof(CXML) );
    if (!ptr) return NULL;
    e_memzero(ptr, sizeof(CXML));
    ptr->attrs = new_fcl_list();
    ptr->data  = new_fcl_list();
    return ptr;
}

CXML_ATTR *
new_cxml_attr()
{
    CXML_ATTR *ptr = malloc( sizeof(CXML_ATTR) );
    if (!ptr) return NULL;
    e_memzero(ptr, sizeof(CXML_ATTR));
    return ptr;
}

int trash_cxml_node(void *v)
{
    CXML_NODE *d = v;
    if(!d) return FALSE;
    new_cstring_free(d->sval);
    new_cstring_free(d->key);
    fcl_list_destroy(d->val);
    fcl_list_destroy(d->attrs);
    e_memfree(d);
    return TRUE;
}

int trash_cxml_attr(void *v)
{
    if (!v) return FALSE;
    CXML_ATTR *ptr = v;
    e_memfree(ptr->key);
    e_memfree(ptr->val);
    e_memfree(ptr);
    return TRUE;
}

int trash_cxml(CXML *v)
{
    if(!v) return FALSE;
    e_memfree(v->tag);
    fcl_list_destroy(v->data);
    fcl_list_destroy(v->attrs);
    e_memfree(v);
    return TRUE;
}

int cxml_parse_attr(FCL_LIST *r, char *s, unsigned long long *i, int root)
{
    if (!r || !s) {
        return FALSE;
    }
    
    unsigned long long p,
                       e,
                       q,
                       d;
    CXML_ATTR         *t;
/**
 * @brief NOTICE
 * Parse the Node attrs array
 */
    q = 0;
    e = 0;
    d = 0;
    p = *i;
    
    for ( ; s[*i] != '\0'; (*i)++ ) {
        
        if ( !q && e_iswhitespace(s[*i]) )
        {
            continue;
        }
/**
 * @brief NOTICE
 * CHAR except the whitespace will do the next work.
 */
        if ( s[*i] == '>' )
        {
            if ( root )
            {
                if ( s[*i-1] != '?' )
                {
                    e_memzero(error_msg, sizeof(error_msg));
                    sprintf(error_msg, "XML header must be closed by: ?>\n");
                    return FALSE;
                }
                else
                {
                    *i += 1;
                    return TRUE;
                }
            }
            else
            {
                if ( s[*i - 1] == '/' ) {
                    *i += 1;
                    return 2; /* Outer space need to pop the stack */
                }
            }
            if ( q ) {
                e_memzero(error_msg, sizeof(error_msg));
                sprintf(error_msg, "Unclosed quote near the char: %c\n", s[d]);
                return FALSE;
            }
            if ( d ) {
                e_memzero(error_msg, sizeof(error_msg));
                sprintf(error_msg, "Unclosed attr near the char: %c\n", s[d]);
                return FALSE;
            }
            
            *i += 1;
            return TRUE;
        }
        elif ( s[*i] == '=' )
        {
            e = *i;
            d = *i;
        }
        elif ( s[*i] == '"' )
        {
            q = ~q;
            
            if ( !q && e )
            {
/**
 * @brief NOTICE
 * To generate the FCL_LIST to store the XML attrs
 */
                t = new_cxml_attr();
                t->key = e_substr(s + p, e - p);
                t->val = e_substr(s + e + 2, *i - e - 2);
                t->key_len =  e - p;
                t->val_len = *i - e - 2;
                fcl_list_push(r, t, trash_cxml_attr);
/**
 * @brief NOTICE
 * After node attrs has been parsed, skip the next white space to do the next parsing.
 */
                (*i)++;
                E_SKIP_SPACE(s, *i);
                p = *i;
                (*i)--;
                e = 0;
                d = 0;
            }
        }
        else
        {
            d = *i;
        }
    }
    
    return FALSE;
}

int cxml_parse_root(CXML *r, char *s, unsigned long long *i)
{
    if (!r || !s) {
        return FALSE;
    }
    
    unsigned long long p;
/**
 * @brief NOTICE
 * Get tag name
 */
    p = *i;
    E_TO_CHAR(s, ' ', *i);
    r->tag = e_substr(s + p, *i - p);
    E_SKIP_SPACE(s, *i);
/**
 * @brief NOTICE
 * Parse the attrs to FCL_LIST structure
 * p : Previous pos.
 * e : The `=` char pos
 * q : The `"` char mode or not.
 * b : The `>` char end mode
 * such as
 * `version="1.0" encoding="UTF-8"`
 */
    return cxml_parse_attr(r->attrs, s, i, TRUE);
}

CXML *new_cxml_from_string2(char *s, unsigned long long len)
{
    CXML              *rcxml;
    CXML_NODE         *cnode,
                      *tnode;
    FCS               *stack;
    /* Position var */
    unsigned long long i, a, al;
    
    /* Status var   */
    unsigned long      x, y;
    unsigned long      c_code, d_code;
/**
 * @brief NOTICE
 * i:  The string `s`'s current position
 */
    if ( !s || s[0] == '\0' ) {
        return NULL;
    }
    
    rcxml = new_cxml();
    if (!rcxml) return NULL;
    
/**
 * @brief NOTICE
 * Parsing the xml data node
 */
    stack = new_fcs();
    cnode = NULL;
    tnode = NULL;
    
    i   = 0;
    E_SKIP_SPACE(s, i);
    
    if ( s[i] == '<' && s[i+1] == '?' )
    {
/**
 * @brief NOTICE
 * Start with: '<?xml ' means the XML header
 * parsing the xml header info
 */
        s += 2;
        if ( !cxml_parse_root(rcxml, s, &i) )
        {
            e_memzero(error_msg, sizeof(error_msg));
            sprintf(error_msg, "XML header syntx wrong.");
            goto trash_result;
        }
    }
    
/**
 * @brief NOTICE
 * To parse the XML node into the CXML structure
 */
    E_SKIP_SPACES(s, i);
    if (s[i] != '<')
    {
        e_memzero(error_msg, sizeof(error_msg));
        sprintf(error_msg, "XML file must be begin with: <");
        goto trash_result;
    }
    
    for ( ; i < len && s[i] != '\0'; ++i )
    {
        if ( estr2cmp(s + i, "</") )
        {
/**
 * @brief NOTICE
 * The last close tag.
 */
            goto new_node_or_close_tag_text;
        }
        elif ( estr4cmp(s + i, "<!--") )
        {
xml_node_comment:
            y = 1;
            for ( i++ ; s[i] != '\0'; i++ )
            {
                if ( estr4cmp(s + i, "<!--") )
                {
                    if ( y ) {
                        e_memzero(error_msg, sizeof(error_msg));
                        sprintf(error_msg, "XML previous comments can't be terminated, another is comming");
                        goto trash_result;
                    }
                }
                elif ( y && estr3cmp(s + i, "-->") )
                {
                    i += 3;
                    y  = 0;
                    break;
                }
            }
            
            E_SKIP_SPACES(s, i);
            i = i - 1;
            if ( y )
            {
                e_memzero(error_msg, sizeof(error_msg));
                sprintf(error_msg, "XML comments not terminated.");
                goto trash_result;
            }
        }
        elif ( s[i] == '<' && (s[i+1] == '_' || e_ischar(s[i+1])) )
        {
            
            cnode = new_cxml_node();
            fcs_push(stack, cnode, NULL);
            
/**
 * @brief NOTICE
 * Node key name
 */
            for ( i++; ;i++ ) {
                
                if ( s[i] == '\0' ) {
                    e_memzero(error_msg, sizeof(error_msg));
                    sprintf(error_msg, "XML open tag unclosed.");
                    goto trash_result;
                }
                elif ( s[i] == '>' || s[i] == ' ' || s[i] == '/' ) {
                    break;
                }
                elif ( e_ischar(s[i]) || e_isdigit(s[i]) || s[i] == ':' || s[i] == '.' || s[i] == '-' || s[i] == '_' )
                {
                    new_cstring_add_char(cnode->key, s[i]);
                }
                else
                {
                    e_memzero(error_msg, sizeof(error_msg));
                    sprintf(error_msg, "XML tag name contains illegal char: %c", s[i]);
                    goto trash_result;
                }
            }
            
            if ( s[i] == ' ' )
            {
                /* Parse the Node attrs */
                E_SKIP_SPACES(s, i);
                x = ( unsigned long )cxml_parse_attr(cnode->attrs, s, &i, FALSE);
                if ( !x )
                {
                    goto trash_result;
                }
                elif ( x == 2 )
                {
                    /* Node close Tag */
                    goto xml_close_tag;
                }
            }
            elif (s[i] == '/' && s[i+1] == '>') /* break from '>' */
            {
                i = i + 1;
                goto xml_close_tag;
            }
            elif (s[i] == '>')
            {
                i = i + 1;
            }
            
new_node_or_close_tag_text:
/**
 * @brief NOTICE
 * Node Text or node close tag or new node
 */
            E_SKIP_SPACES(s, i);
            
            if ( s[i] == '<' && (s[i+1] == '_' || e_ischar(s[i+1]) ) )
            {
                /* New XML node */
                i = i - 1;
                continue;
            }
            elif ( s[i] == '<' && s[i+1] == '/' )
            {
                /* Node close tag */
                /* Compare the TAG is same with the before one */
                i = i + 2;
                a = i; /* key start position. */
                for ( ; s[i] != '\0' ; i++)
                {
                    if ( s[i] == '>' && (s[i-1] != '/' || s[i-1] == '<' ) )
                    {
                        break;
                    }
                    elif ( e_ischar(s[i]) || s[i] == ':' || s[i] == '_' || s[i] == '-' || s[i] == '.' ) continue;
                    else
                    {
                        e_memzero(error_msg, sizeof(error_msg));
                        sprintf(error_msg, "XML node close tag contain illegal char: %c", s[i]);
                        goto trash_result;
                    }
                }
/**
 * @brief NOTICE
 * See the closed tag is same with the the open tag.
 * al : The length of the current closed tag length.
 */
                al = i - a;
                if ( e_memcmp(cnode->key->s, s + a, al > cnode->key->l ? al : cnode->key->l) != 0 )
                {
                    e_memzero(error_msg, sizeof(error_msg));
                    sprintf(error_msg, "XML open tag: %s mismatch the close tag:", cnode->key->s);
                    snprintf(error_msg + 38 + cnode->key->l, al + 1, "%s", s + a);
                    goto trash_result;
                }
xml_close_tag:
                i = i + 1;
                
                if ( FCS_HEAD_P(stack) && FCN_NEXT_P(FCS_HEAD_P(stack)) )
                {
                    cnode = FCN_DATA_P(FCN_NEXT_P(FCS_HEAD_P(stack)));
                    cnode->vtp = CXML_NTYPE;
                    tnode = fcs_pop(stack);
                    fcl_list_push(cnode->val, tnode, trash_cxml_node);
                    
                    goto text_node;
                }
                else
                {
                    tnode = fcs_pop(stack);
                    fcl_list_push(rcxml->data, tnode, trash_cxml_node);
                    
                    E_SKIP_SPACES(s, i);
                    i = i - 1;
                }
            }
            else
            {
/**
 * @brief NOTICE
 * Text node.
 */
text_node:
                E_SKIP_SPACES(s, i);
                
                c_code = 0; /* <!--      */
                d_code = 0; /* <![CDATA[ */
                
                for ( ; i < len ; i++ )
                {
                    /* Deal the Node Text node */
                    if ( !d_code && estr4cmp(s + i, "<!--") )
                    {
                        if ( !c_code )
                        {
                            c_code = 1;
                            i     += 3;
                        }
                        else
                        {
                            e_memzero(error_msg, sizeof(error_msg));
                            sprintf(error_msg, "XML comments not terminated.");
                            goto trash_result;
                        }
                    }
                    
                    if ( c_code )
                    {
                        if ( estr3cmp(s + i, "-->") )
                        {
                            i       += 2;
                            c_code   = 0;
                        }
                        continue;
                    }
                    else
                    {
                        if ( estr9cmp(s + i, "<![CDATA[") )
                        {
                            if ( !d_code )
                            {
                                d_code  = 1;
                                i      += 8;
                            }
                            else
                            {
                                e_memzero(error_msg, sizeof(error_msg));
                                sprintf(error_msg, "XML CDATA tag not terminated.");
                                goto trash_result;
                            }
                        }
                        elif ( estr3cmp(s + i, "]]>") )
                        {
                            if ( !d_code )
                            {
                                e_memzero(error_msg, sizeof(error_msg));
                                sprintf(error_msg, "XML unopend CDATA tag: ]]>");
                                goto trash_result;
                            }
                            else
                            {
                                d_code = 0;
                                i     += 2;
                            }
                        }
                        else
                        {
                            if ( estr2cmp(s + i, "</") )
                            {
                                if ( c_code || d_code )
                                {
                                    e_memzero(error_msg, sizeof(error_msg));
                                    sprintf(error_msg, "XML comment or CDATA tag not terminated.");
                                    goto trash_result;
                                }
                                
                                goto new_node_or_close_tag_text;
                            }
                            elif ( s[i] == '<' && ( s[i+1] == '_' || e_ischar(s[i+1]) ) )
                            {
                                if ( c_code || d_code )
                                {
                                    e_memzero(error_msg, sizeof(error_msg));
                                    sprintf(error_msg, "XML comment or CDATA tag not terminated.");
                                    goto trash_result;
                                }
                                goto new_node_or_close_tag_text;
                            }
                            new_cstring_add_char(cnode->sval, s[i]);
                        }
                    }
                }
                
                if ( c_code )
                {
                    e_memzero(error_msg, sizeof(error_msg));
                    sprintf(error_msg, "XML comments not terminated.");
                    goto trash_result;
                }
/**
 * @brief NOTICE
 * if program run to here, send error message to it.
 */
                e_memzero(error_msg, sizeof(error_msg));
                sprintf(error_msg, "XML node key: [%s] not closed", cnode->key->s);
                goto trash_result;
            }
        }
        else
        {
            e_memzero(error_msg, sizeof(error_msg));
            sprintf(error_msg, "XML node key must be start with < and char or _");
            goto trash_result;
        }
    }

/**
 * @brief NOTICE
 * To find is the multi root XML or not
 */
    if (FCL_LIST_NUM_P(rcxml->data) > 1) {
        rcxml->xtpe = CXML_MULTI_ROOT_TAG;
    }
    fcs_destroy(stack);
    return rcxml;

trash_result:
    fcs_destroy(stack);
    trash_cxml(rcxml);
    return NULL;
}

static
void _list_to_xml(FCL_LIST *v, CSTRING *res)
{
    if (!res || !v) {
        return ;
    }
    
    FCL_NODE    *fnode,
                *ffode;
    CXML_NODE   *cnode;
    CXML_ATTR   *cattr;
    
    FCL_LIST_FOREACH_HEAD(v, fnode) {
        
        cnode = FCL_NODE_DATA_P(fnode);
        if ( !cnode ) { continue; }
        
        new_cstring_add_string(res, E_STRL("<"));
        new_cstring_add_string( res, cnode->key->s, ( long )cnode->key->l );
        
        if ( cnode->attrs->num ) {
    
            new_cstring_add_string(res, E_STRL(" "));
            
            FCL_LIST_FOREACH_HEAD(cnode->attrs, ffode) {
                cattr = FCL_NODE_DATA_P(ffode);
                if (!cattr) {
                    continue;
                }
                new_cstring_add_string(res, cattr->key, cattr->key_len);
                new_cstring_add_string(res, E_STRL("=\""));
                new_cstring_add_string(res, cattr->val, cattr->val_len);
                new_cstring_add_string(res, E_STRL("\""));
                if ( FCL_NODE_NEXT_P(ffode) )
                {
                    new_cstring_add_string(res, E_STRL(" "));
                }
            } FCL_LIST_FOREACH_END();
        }
        new_cstring_add_string(res, E_STRL(">"));
        
        if (cnode->sval->s && cnode->sval->l)
        {
            new_cstring_add_string(res, E_STRL("<![CDATA["));
            new_cstring_add_string( res, cnode->sval->s, ( long )cnode->sval->l );
            new_cstring_add_string(res, E_STRL("]]>"));
        }
        
        if ( cnode->val->num )
        {
            _list_to_xml(cnode->val, res);
    
            new_cstring_add_string(res, E_STRL("</"));
            new_cstring_add_string( res, cnode->key->s, ( long )cnode->key->l );
            new_cstring_add_string(res, E_STRL(">"));
        }
        else
        {
            new_cstring_add_string(res, E_STRL("</"));
            new_cstring_add_string( res, cnode->key->s, ( long )cnode->key->l );
            new_cstring_add_string(res, E_STRL(">"));
        }
        
    } FCL_LIST_FOREACH_END();
}

char *new_string_from_cxml(CXML *c)
{
    if ( !c ) {
        return NULL;
    }
    
    char        *r;
    CSTRING     *res;
    FCL_NODE    *fnode;
    CXML_ATTR   *cattr;
    
    res = new_cstring();
    
    if ( c->attrs->num ) {
/**
 * @brief NOTICE
 * Generate XML header Line
 */
        new_cstring_add_string(res, E_STRL("<?"));
        new_cstring_add_string(res, c->tag, strlen(c->tag));
        new_cstring_add_string(res, E_STRL(" "));
        
        FCL_LIST_FOREACH_HEAD(c->attrs, fnode) {
            cattr = FCL_NODE_DATA_P(fnode);
            if (!cattr) {
                continue;
            }
            new_cstring_add_string(res, cattr->key, cattr->key_len);
            new_cstring_add_string(res, E_STRL("=\""));
            new_cstring_add_string(res, cattr->val, cattr->val_len);
            new_cstring_add_string(res, E_STRL("\""));
            if ( FCL_NODE_NEXT_P(fnode) ) {
                new_cstring_add_string(res, E_STRL(" "));
            }
        } FCL_LIST_FOREACH_END();
        new_cstring_add_string(res, E_STRL("?>"));
    }
    
/**
 * @brief NOTICE
 * Generate the data list
 */
    if (c->data) {
      _list_to_xml(c->data, res);
    }

    r = res->s;
    e_memfree(res);
    return r;
}

inline
char *new_cxml_get_error()
{
    return error_msg;
}

/**
 * @brief NOTICE
 * Below are some APIs for generate the XML doc CXML structure
 */
CXML_API CXML_NODE *cxml_make_node(char *name, unsigned long nlen, char *text, unsigned long tlen)
{
    if ( !name || nlen <= 0 )
    {
        return NULL;
    }
    
    CXML_NODE *r;
    
    r = new_cxml_node();
    
    new_cstring_add_string(r->key, name, nlen);
    if ( text && tlen > 0 )
    {
        new_cstring_add_string(r->sval, text, tlen);
    }
    
    return r;
}

CXML_API int cxml_node_add_attr(CXML_NODE *cnode, char *key, unsigned long klen, char *val, unsigned long vlen)
{
    if ( !cnode || !key || klen <= 0 || !val )
    {
        return FALSE;
    }
    
    CXML_ATTR *attr;
    attr = new_cxml_attr();
    
    attr->key = malloc( sizeof(char) * (klen + 1));
    e_memzero(attr->key, sizeof(char) * (klen + 1));
    e_copymem(attr->key, key, sizeof(char) * klen);
    attr->key_len = klen;
    
    attr->val = malloc( sizeof(char) * (vlen + 1));
    e_memzero(attr->val, sizeof(char) * (vlen + 1));
    e_copymem(attr->val, key, sizeof(char) * vlen);
    attr->val_len = vlen;
    
    fcl_list_push(cnode->attrs, attr, trash_cxml_attr);
    
    return TRUE;
}

CXML_API int cxml_node_add_node(CXML_NODE *cnode, CXML_NODE *snode)
{
    if ( !cnode || !snode )
    {
        return FALSE;
    }
    
    fcl_list_push(cnode->val, snode, trash_cxml_node);
    return TRUE;
}

CXML_API CXML *cxml_new_cxml(char *tag, char *version, char *encoding)
{
    CXML *xml = new_cxml();
    
    if ( tag ) {
        xml->tag = malloc( sizeof(char) * (strlen(tag + 1)) );
        e_copymem(xml->tag, tag, strlen(tag));
        xml->tag[strlen(tag)] = '\0';
    }
    
    CXML_ATTR *attr;
    
    if ( version )
    {
        attr = new_cxml_attr();
        
        attr->key = malloc( sizeof(char) * ( 8 ) );
        e_memzero(attr->key, sizeof(char) * ( 8 ) );
        e_copymem(attr->key, "version", sizeof(char) * 7 );
    
        attr->val = malloc( sizeof(char) * (strlen(version) + 1));
        e_memzero(attr->val, sizeof(char) * ( strlen(version) + 1) );
        e_copymem(attr->val, version, sizeof(char) * strlen(version));
        
        fcl_list_push(xml->attrs, attr, trash_cxml_attr);
    }
    
    if ( encoding )
    {
        attr = new_cxml_attr();
    
        attr->key = malloc( sizeof(char) * ( 9 ) );
        e_memzero(attr->key, sizeof(char) * ( 9 ) );
        e_copymem(attr->key, "encoding", sizeof(char) * 8);
    
        attr->val = malloc( sizeof(char) * (strlen(encoding) + 1));
        e_memzero(attr->val, sizeof(char) * ( strlen(encoding) + 1) );
        e_copymem(attr->val, encoding, sizeof(char) * strlen(encoding));
    
        fcl_list_push(xml->attrs, attr, trash_cxml_attr);
    }
    
    return xml;
}

CXML_API int cxml_add_root_node(CXML *xml, CXML_NODE *node)
{
    if (!xml || !node) return FALSE;
    
    fcl_list_push(xml->data, node, trash_cxml_node);
    
    if ( FCL_LIST_NUM_P(xml->data) > 1)
    {
        xml->xtpe = CXML_MULTI_ROOT_TAG;
    }
    else
    {
        xml->xtpe = CXML_SIMPLE_ROOT_TAG;
    }
    return TRUE;
}